package TLog

import (
	"mylogger/TLog/logcore"
	"os"
	"strings"
	"sync"
	"sync/atomic"
)

type Level uint32
type Flag uint32

//log level, from low to high, more high means more serious
const (
	LevelTrace Level = iota
	LevelDebug
	LevelInfo
	LevelWarn
	LevelError
	LevelPanic
	LevelFatal
)

const (
	LDateTime  Flag = 1 << iota // the date in the local time zone: 2009/01/23
	Lcaller                     // full file name and line number: /a/b/c/d.go:23
	Lshortfile                  // final file name element and line number: d.go:23. overrides Llongfile
	LUTC                        // if LDateTime or Ltime is set, use UTC rather than the local time zone
	PrefixHead
	PrefixMid // move the "prefix" from the beginning of the line to after the datetime
	PrefixMsg // move the "prefix" from the beginning of the line to before the message
)

var LevelName = [7]string{"TRACE", "DEBUG", "INFO", "WARN", "ERROR", "PANIC", "FATAL"}

const maxBufPoolSize = 16

type Logger struct {
	// Logger level in ( LevelTrace  LevelDebug LevelInfo LevelWarn LevelError LevelPanic LevelFatal )
	Level     Level
	Flag      Flag
	Sing      string
	IsCaller  bool
	hMutex    sync.Mutex
	Handler   logcore.Handler
	bufMutex  sync.Mutex
	bufs      [][]byte
	closed    uint32
	Formatter logcore.Formatter
}

//new a logger with specified Handler and flag
func New(handler logcore.Handler, format logcore.Formatter, flag Flag) *Logger {
	var l = new(Logger)
	atomic.StoreUint32((*uint32)(&l.Level), uint32(LevelInfo))
	l.Handler = handler
	l.Flag = flag
	l.Formatter = format
	atomic.StoreUint32(&l.closed, 0)
	l.bufs = make([][]byte, 0, 16)
	return l
}

//new a default logger with specified Handler and flag: Ltime|Lfile|Llevel
func DefaultLogger() *Logger {
	return New(newStdHandler(), new(logcore.StandardFormatter), LDateTime|Lcaller|PrefixHead|Lshortfile)
}

func newStdHandler() *logcore.StreamHandler {
	h, _ := logcore.NewStreamHandler(os.Stdout)
	return h
}

var std = DefaultLogger()

func (l *Logger) popBuf() []byte {
	l.bufMutex.Lock()
	var buf []byte
	if len(l.bufs) == 0 {
		buf = make([]byte, 0, 1024)
	} else {
		buf = l.bufs[len(l.bufs)-1]
		l.bufs = l.bufs[0 : len(l.bufs)-1]
	}
	l.bufMutex.Unlock()

	return buf
}

func (l *Logger) putBuf(buf []byte) {
	l.bufMutex.Lock()
	if len(l.bufs) < maxBufPoolSize {
		buf = buf[0:0]
		l.bufs = append(l.bufs, buf)
	}
	l.bufMutex.Unlock()
}

func (l *Logger) Close() {
	if atomic.LoadUint32(&l.closed) == 1 {
		return
	}
	atomic.StoreUint32(&l.closed, 1)

	l.Handler.Close()
}

//set log level, any log level less than it will not log
func (l *Logger) SetLevel(level Level) {
	atomic.StoreUint32((*uint32)(&l.Level), uint32(level))
}
func (l *Logger) GetLevel() Level {
	return Level(atomic.LoadUint32((*uint32)(&l.Level)))
}

func (l *Logger) SetFlag(flag Flag) {
	l.Flag = flag
}

// name can be in ["trace", "debug", "info", "warn", "error", "fatal"]
func (l *Logger) SetLevelByName(name string) {
	name = strings.ToLower(name)
	switch name {
	case "trace":
		l.SetLevel(LevelTrace)
	case "debug":
		l.SetLevel(LevelDebug)
	case "info", "Info", "INFO":
		l.SetLevel(LevelInfo)
	case "warn":
		l.SetLevel(LevelWarn)
	case "error":
		l.SetLevel(LevelError)
	case "panic":
		l.SetLevel(LevelPanic)
	case "fatal":
		l.SetLevel(LevelFatal)
	}
}

func (l *Logger) SetHandler(h logcore.Handler) {
	if atomic.LoadUint32(&l.closed) == 1 {
		return
	}

	l.hMutex.Lock()
	if l.Handler != nil {
		_ = l.Handler.Close()
	}
	l.Handler = h
	l.hMutex.Unlock()
}

func (l *Logger) Output(callDepth int, level Level, format string, v ...interface{}) {
	if atomic.LoadUint32(&l.closed) == 1 {
		return
	}

}

//log with Trace level
func (l *Logger) Trace(v ...interface{}) {
	l.Output(2, LevelTrace, "", v...)
}

//log with Debug level
func (l *Logger) Debug(v ...interface{}) {
	l.Output(2, LevelDebug, "", v...)
}

//log with info level
func (l *Logger) Info(v ...interface{}) {
	l.Output(2, LevelInfo, "", v...)
}

//log with warn level
func (l *Logger) Warn(v ...interface{}) {
	l.Output(2, LevelWarn, "", v...)
}

//log with error level
func (l *Logger) Error(v ...interface{}) {
	l.Output(2, LevelError, "", v...)
}

//log with fatal level
func (l *Logger) Fatal(v ...interface{}) {
	l.Output(2, LevelFatal, "", v...)
}

//log with Trace level
func (l *Logger) Tracef(format string, v ...interface{}) {
	l.Output(2, LevelTrace, format, v...)
}

//log with Debug level
func (l *Logger) Debugf(format string, v ...interface{}) {
	l.Output(2, LevelDebug, format, v...)
}

//log with info level
func (l *Logger) Infof(format string, v ...interface{}) {
	l.Output(2, LevelInfo, format, v...)
}

//log with warn level
func (l *Logger) Warnf(format string, v ...interface{}) {
	l.Output(2, LevelWarn, format, v...)
}

//log with error level
func (l *Logger) Errorf(format string, v ...interface{}) {
	l.Output(2, LevelError, format, v...)
}

//log with fatal level
func (l *Logger) Fatalf(format string, v ...interface{}) {
	l.Output(2, LevelFatal, format, v...)
}

func SetLevel(level Level) {
	std.SetLevel(level)
}

// name can be in ["trace", "debug", "info", "warn", "error", "fatal"]
func SetLevelByName(name string) {
	std.SetLevelByName(name)
}

func SetHandler(h logcore.Handler) {
	std.SetHandler(h)
}

func Trace(v ...interface{}) {
	std.Output(2, LevelTrace, "", v...)
}

func Debug(v ...interface{}) {
	std.Output(2, LevelDebug, "", v...)
}

func Info(v ...interface{}) {
	std.Output(2, LevelInfo, "", v...)
}

func Warn(v ...interface{}) {
	std.Output(2, LevelWarn, "", v...)
}

func Error(v ...interface{}) {
	std.Output(2, LevelError, "", v...)
}

func Fatal(v ...interface{}) {
	std.Output(2, LevelFatal, "", v...)
}

func Tracef(format string, v ...interface{}) {
	std.Output(2, LevelTrace, format, v...)
}

func Debugf(format string, v ...interface{}) {
	std.Output(2, LevelDebug, format, v...)
}

func Infof(format string, v ...interface{}) {
	std.Output(2, LevelInfo, format, v...)
}

func Warnf(format string, v ...interface{}) {
	std.Output(2, LevelWarn, format, v...)
}

func Errorf(format string, v ...interface{}) {
	std.Output(2, LevelError, format, v...)
}

func Fatalf(format string, v ...interface{}) {
	std.Output(2, LevelFatal, format, v...)
}
